package com.yxn.cloud.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.yxn.cloud.product.service.CategoryBrandRelationService;
import com.yxn.cloud.product.vo.Catalog2Vo;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yxn.common.utils.PageUtils;
import com.yxn.common.utils.Query;

import com.yxn.cloud.product.dao.CategoryDao;
import com.yxn.cloud.product.entity.CategoryEntity;
import com.yxn.cloud.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Resource
    CategoryBrandRelationService categoryBrandRelationService;

    @Resource
    StringRedisTemplate stringRedisTemplate;

    @Resource
    RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        /** 1、首先查询出所有的商品信息 */
        List<CategoryEntity> list = baseMapper.selectList(null);

        /** 2、组装成父子的树形结构*/

        /**2.1、 先获取所有的一级分类*/
        List<CategoryEntity> firstLevel = list.stream().filter((categoryEntity) -> {
            return categoryEntity.getParentCid() == 0;
        }).map((entity) -> {
            entity.setChildren(getChildren(entity, list));
            return entity;
        }).sorted((entity1,entity2) -> {
            return (entity1.getSort() == null ? 0 :entity1.getSort()) - (entity2.getSort() == null ? 0 :entity2.getSort());
        }).collect(Collectors.toList());

        return firstLevel;
    }

    @Override
    public void batchRemoveMenuByIds(List<Long> asList) {
        //todo 1、需要先对这些id的菜单先做判断
        baseMapper.deleteBatchIds(asList);
    }

    @Override
    public Long[] findCategoryPath(Long catelogId) {
        ArrayList<Long> lists = new ArrayList<Long>();
        List<Long> path = findParentPath(catelogId, lists);
        Collections.reverse(path);
        return lists.toArray(new Long [path.size()]);
    }

    /**
     * 整合SpringCache 使用删除缓存来演示失效模式
     * @CacheEvict(value = "category",key = "selectLevel1Category") 指定删除哪个片区下的哪个 key
     * 当然 使用双写模式可使用 @CachePut 注解
     * @param category
     */

    @Caching(evict = {
            @CacheEvict(value = "category",key = "'selectLevel1Category'"),
            @CacheEvict(value = "category",key = "'getCatalogJson'")
    })
    @Transactional
    @Override
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
        categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
    }

    /**
     * 每一个需要缓存的数据都需要指定放入到什么分区当中
     * 加了此注解之后表示当前方法需要缓存，当缓存中有的话，就从缓存中获取，如果缓存中没有，就从数据库获取
     * 然后再次放入到缓存中
     * key是默认生成的
     * @return
     */


    @Cacheable(value = "category",key = "'selectLevel1Category'",sync = true)
    @Override
    public List<CategoryEntity> selectLevel1Category() {
        System.out.println("category1.....");
        List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        return categoryEntities;
    }

    @Cacheable(value = "category", key = "#root.methodName")
    @Override
    public Map<String, List<Catalog2Vo>> getCatalogJson() {
        System.out.println("开始查询数据库");
        /**查询一次数据库，然后进行递归查询 这样可以减少磁盘IO*/
        List<CategoryEntity> selectList = baseMapper.selectList(null);

        //查出所有的一级分类
        List<CategoryEntity> category1 = getParent_cid(selectList, 0L);
        return category1.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            /**查询当前分类下的所有子分类*/
            List<CategoryEntity> category2 = getParent_cid(selectList, v.getCatId());
            List<Catalog2Vo> lists = null;
            if (null != category2) {
                lists = category2.stream().map(entity -> {
                    Catalog2Vo vo = new Catalog2Vo
                            (v.getCatId().toString(), null, entity.getCatId(), entity.getName());
                    List<CategoryEntity> category3 =
                            getParent_cid(selectList, entity.getCatId());
                    List<Catalog2Vo.Catalog3Vo> category3List = null;
                    if (null != category3) {
                        category3List = category3.stream().map(entity3 -> {
                            Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(entity.getCatId().toString(),
                                    entity3.getCatId().toString(), entity3.getName());
                            return catalog3Vo;
                        }).collect(Collectors.toList());

                    }
                    vo.setCatalog3List(category3List);
                    return vo;
                }).collect(Collectors.toList());
            }
            return lists;
        }));
    }

    public Map<String, List<Catalog2Vo>> getCatalogJson2() {
        //给缓存中放的是json，拿出来之后还需要解析  序列化与反序列化
        /**
         * 1、缓存穿透：加入null值，防止恶意请求打到MySQL上
         * 2、缓存雪崩：加一个过期随机时间，解决缓存雪崩
         * 3、缓存击穿：加锁（分布式）
         /
        /**1、加入缓存逻辑 缓存中存储的是json字符串*/
        String catalogJson = stringRedisTemplate.opsForValue().get("catalogJson");
        if(StringUtils.isEmpty(catalogJson)){
            /**2、缓存中没有，将数据设置到缓存中*/
            Map<String, List<Catalog2Vo>> catalogJsonFromDb = getCatalogJsonFromDbWithRedislock();
            String jsonString = JSON.toJSONString(catalogJsonFromDb);
            stringRedisTemplate.opsForValue().set("catalogJson", jsonString,1, TimeUnit.DAYS);
            return catalogJsonFromDb;
        }
        //转换为对应的类型
        Map<String, List<Catalog2Vo>> result = JSON.parseObject
                (catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
        });
        return result;
    }

    public Map<String, List<Catalog2Vo>> getCatalogJsonFromDbWithRedislock() {
        // 加锁一定要细粒度，比如具体缓存的某个商品 product-11-lock
        RLock lock = redissonClient.getLock("CatalogJson-lock");
        lock.lock();
        //1 抢占分布式锁
            Map<String, List<Catalog2Vo>> listMap = null;
            try {
                listMap = getFromDb();
            } finally {
                lock.unlock();
            }

        return getCatalogJsonFromDbWithRedislock();
    }

    /**从数据库获取信息*/
    public Map<String, List<Catalog2Vo>> getCatalogJsonFromDbWithLocallock() {
        //因为Spring 的bean都是单例的，所以可以使用Synchronized 来保证锁住所有的线程
       synchronized (this){
           /**同时还需要再次查询，确保缓存中是否存在，因为在高并发下，可能上一个刚释放锁的人已经将数据加入到了缓存中*/
           String catalogJson = stringRedisTemplate.opsForValue().get("catalogJson");
           if(StringUtils.isEmpty(catalogJson)){
               /**2、缓存中没有，将数据设置到缓存中*/
               Map<String, List<Catalog2Vo>> result = JSON.parseObject
                       (catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
                       });
               return result;
           }
           Map<String, List<Catalog2Vo>> parent_cid = getFromDb();
           /**将数据放入到缓存中，作为一个原子操作执行*/
           String jsonString = JSON.toJSONString(parent_cid);
           stringRedisTemplate.opsForValue().set("catalogJson", jsonString,1, TimeUnit.DAYS);
           return parent_cid;
       }
    }

    private Map<String, List<Catalog2Vo>> getFromDb() {
        String catalogJson = stringRedisTemplate.opsForValue().get("catalogJson");
        if(!StringUtils.isEmpty(catalogJson)){
            Map<String, List<Catalog2Vo>> result = JSON.parseObject
                    (catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
                    });
            return result;
        }
        System.out.println("开始查询数据库");
        /**查询一次数据库，然后进行递归查询 这样可以减少磁盘IO*/
        List<CategoryEntity> selectList = baseMapper.selectList(null);

        //查出所有的一级分类
        List<CategoryEntity> category1 = getParent_cid(selectList, 0L);
        return category1.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            /**查询当前分类下的所有子分类*/
            List<CategoryEntity> category2 = getParent_cid(selectList, v.getCatId());
            List<Catalog2Vo> lists = null;
            if (null != category2) {
                lists = category2.stream().map(entity -> {
                    Catalog2Vo vo = new Catalog2Vo
                            (v.getCatId().toString(), null, entity.getCatId(), entity.getName());
                    List<CategoryEntity> category3 =
                            getParent_cid(selectList, entity.getCatId());
                    List<Catalog2Vo.Catalog3Vo> category3List = null;
                    if (null != category3) {
                        category3List = category3.stream().map(entity3 -> {
                            Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(entity.getCatId().toString(),
                                    entity3.getCatId().toString(), entity3.getName());
                            return catalog3Vo;
                        }).collect(Collectors.toList());

                    }
                    vo.setCatalog3List(category3List);
                    return vo;
                }).collect(Collectors.toList());
            }
            return lists;
        }));
    }

    private List<CategoryEntity> getParent_cid(List<CategoryEntity> selectList,Long parent_cid) {
        List<CategoryEntity> collect = selectList.stream().filter(itme -> parent_cid.equals(itme.getParentCid())).collect(Collectors.toList());
//        return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", parent_cid));
        return  collect;
    }

    /**255,13,5  需要反转过来*/
    private List<Long> findParentPath(Long catelogId, ArrayList<Long> lists) {
        lists.add(catelogId);
        CategoryEntity byId = this.getById(catelogId);
        if (byId.getParentCid() != 0){
            findParentPath(byId.getParentCid(),lists);
        }
        return lists;
    }


    /**通过递归的方法实现找对应的子菜单*/
    private List<CategoryEntity> getChildren(CategoryEntity root,List<CategoryEntity> all){
        /**首先进行过滤判断*/
        List<CategoryEntity> children = all.stream().filter((categoryEntity) -> {
            return categoryEntity.getParentCid().equals(root.getCatId());
        }).map((entity) -> {
            entity.setChildren(getChildren(entity, all));
            return entity;
        }).sorted((entity1,entity2) -> {
            return (entity1.getSort() == null ? 0 :entity1.getSort()) - (entity2.getSort() == null ? 0 :entity2.getSort()) ;
        }).collect(Collectors.toList());
        return children;
    }

}
